JavaScript精要之函数

1.函数的本质
JavaScript中,函数其实就是对象,使函数不同于其他对象的决定性特点就是函数存在一个[[call]]的内部属性。内部属性无法通过代码访问,而是定义了代码执行时的行为。(JavaScript的对象定义了多种内部属性,这种属性都用双重中括号括起来,比如[[Prototype]])。正是有了这样的一个[[call]]属性,所以typeof在解读时,把函数解读为function。

2.函数的提升
我们都知道,定义一个函数,既可以使用函数声明,也可以使用函数表达式,或者是使用构造函数方式。值得注意的是,函数声明会被提升至上下文!

1
2
3
4
var result = add(5,5);//10
function add(num1,num2){
return num1 + num2;
}

JavaScript会将函数声明提升至顶部来执行,使得它就如同下面这代码一样:

1
2
3
4
function add(num1,num2){
return num1 + num2;
}
var result = add(5,5);

但是,值得注意的是,函数表达式,或者构造函数必须得在函数定义后调用函数,下面这就是错的:

1
2
3
4
var result = add(5,5);
var add = function(num1,num2){
return num1 + num2;
};

3.函数就是值!
eg1.函数可以将他们赋给变量:

1
2
3
4
5
6
function sayHi(){
alert("hi");
}
sayHi();//hi
var sayHi1 = sayHi;
sayHi1();

eg2.可以将函数当成参数传递给别的函数,或者从别的函数中返回

1
2
3
4
5
6
7
8
9
var numbers = [1,8,3,10,6];
numbers.sort(function(first,second){
return first - second;
});
console.log(numbers);//[1,3,6,8,10]


numbers.sort();
console.log(numbers);//[1,10,3,6,8]

4.函数的参数
JavaScript函数的一个独特之处,在于你可以给函数传递任意数量的参数,却不会造成错误。原因是,有一个arguments对象,自动存在于函数中。这个arguments对象,类似于数组,用来存放函数的参数。arguments的length值会告诉你到底有多少个参数的。注意,arguments对象只是类似于数组,但是并不是数组,因此console.log(Array.isArray(arguments));始终返回false。
eg1.

1
2
3
4
5
6
7
8
9
10
11
12
13
function reflect(value){
return value;
}
console.log(reflect("hi"));//hi
console.log(reflect("hi",20));//hi
console.log(reflect.length);//1

reflect = function(){
return arguments[0];
}
console.log(reflect("hi");//hi
console.log(reflect("hi",20));//hi
console.log(reflect.length);//0

eg2.

1
2
3
4
5
6
7
8
9
10
11
function sum(){
var result = 0,i = 0,len = arguments.length;
while(i < len){
result += arguments[i];
}
return result;
}
console.log(sum(1,2));//3
console.log(sum(3,4,5,6));//18
console.log(sum(50));//50
console.log(sum(0));//0

5.函数的重载
JavaScript不存在函数的重载。比如:

1
2
3
4
5
6
7
function sayMessage(message){
console.log(message);
}
function sayMessage(){
console.log("Default message");
}
sayMessage("hello");//Default message

也就是说,只有最近定义的那个函数起作用,重复的那个会被最近的那个覆盖。本质如:

1
2
3
4
var sayMessage = new Function("message","console.log(message)");
sayMessage = new Function("console.log(\"Default message"\)");//
后一个覆盖了上一个了
sayMessage("hello");//Default message

模仿重载:

1
2
3
4
5
6
7
8
function sayMessage(message){
if (arguments.length == 0){
//这里还可以使用typeof、instanceof来检测判断
message = "Default message";
}
console.log(message);
}
sayMessage("hello");//hello

6.This
首先,看一下不使用this的时候,会出现的情况:

1
2
3
4
5
6
7
var person(){
name : "cooling",
sayName() : function(){
console.log(person.name);
}
};
person.sayName();

问题:方法和对象上存在紧密耦合。①你改变了对象,也就是改变了person变量,那么你将不得不改变方法中的引用的名字②同一个方法,无法做到被不同的对象来使用

改进:

1
2
3
4
5
6
7
var person(){
name : "cooling",
sayName() : function(){
console.log(this.name);
}
};
person.sayName();

整合,体现this用法的代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sayNameforall(){
console.log(this.name);
}
var person1 = {
name : "cooling1",
sayName : sayNameforall
};
var person2 = {
name : "cooling2",
sayName : sayNameforall
};
var name = "cooling";
person1.sayName();//cooling1
person2.sayName();.//cooling2
sayNameforall();//cooling

7.改变This
①call()方法:
第一个参数指定了执行时this的值,其后的所有参数都是需要被传入函数的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function sayNameForAll(lable){
console.log(lable + ":" + this.name);
}
var person1 = {
name : "cooling1"
};
var person2 = {
name : "cooling2";
};
var name = "cooling";
sayNameForAll.call(this,"global");//cooling
sayNameForAll.call(person1,"person1");//cooling1
sayNameForAll.call(person2,"person2");//cooling2

②apply()方法:
这里的第二个参数是数组,数组可以是多个,所以传入函数的不仅仅是lable的话,那么多个数组就会起到作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
function sayNameForAll(lable){
console.log(lable + ":" + this.name);
}
var person1 = {
name : "cooling1"
};
var person2 = {
name : "cooling2";
};
var name = "cooling";
sayNameForAll.apply(this,["global"]);//cooling
sayNameForAll.apply(person1,["person1"]);//cooling1
sayNameForAll.apply(person2,["person2"]);//cooling2

③bind()方法:

注意:这种方式,必须将其赋给另一个变量,然后利用这个变量,才可以进行执行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function sayNameForAll(lable){
console.log(lable + ":" + this.name);
}
var person1 = {
name : "cooling1";
};
var person2 = {
name : "cooling2";
};

var sayNameForPerson1 = sayNameForAll.bind(person1);
sayNameForPerson1("person1");//person1:cooling1

var sayNameForPerson2 = sayNameForAll.bind(person2,"person2");
sayNameForPerson2();//person2:cooling2

person2.sayName = sayNameForPerson1;
person2.sayName("person2");//person2:cooling1

实际上,最后两行就相当于:
var sayNameForPerson1 = sayNameForAll.bind(person1);
sayNameForPerson1("person2");